package spider

import (
	"asocrawler/conf"
	"asocrawler/db"
	"asocrawler/sendwarn"
	"asocrawler/tool"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (

	//登录
	CasLoginUrl = "http://8.134.139.204/cas/api/login"
	//App推广榜 游戏列表
	CasGameList = "http://8.134.139.204/cas/api/app/promote/list"
	//游戏素材详情
	CasGameInfo = "http://8.134.139.204/cas/api/app/material/list"
	//域名
	CasDomain = "http://8.134.139.204/"
)

type CasSpider struct {
	Client        *http.Client
	ChannelName   string
	ExecutionTime []int
}

//记录验证码报警状态
var CasWarningStatus bool

func NewCasSpider() (c *CasSpider) {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return
	}

	c = &CasSpider{
		ChannelName:   "Cas-overseas",
		ExecutionTime: conf.Conf.CasConfig.ExecutionTime,
		Client: &http.Client{
			Jar: jar,
		},
	}
	return
}

func (this *CasSpider) Println(v ...interface{}) {
	var str = fmt.Sprintf("[%s]", this.ChannelName)
	if conf.Conf.Debug.Switch {
		str += "-[Debug]"
	}
	log.Println(str, fmt.Sprintln(v...))
}

//登录信息
type LoginInfo struct {
	Autologin bool   `json:"autologin"`
	Email     string `json:"email"`
	Isrember  bool   `json:"isrember"`
	Password  string `json:"password"`
	Source    int    `json:"source"`
	IsCheck   int    `json:"isCheck"`
	Version   string `json:"version"`
}

type CasLoginResult struct {
	Code    interface{} `json:"code"`
	Message string      `json:"message"`
	Content struct {
		Token string `json:"TOKEN"`
	} `json:"content"`
}

func (c *CasSpider) GetCasToken() (tokenStr string, err error) {

	count := 0
re:
	param := `{"autologin":true,"email":"%s","isrember":false,"password":"%s","source":1,"isCheck":1,"version":"1.1"}`
	body := fmt.Sprintf(param, conf.Conf.CasConfig.UserId, strings.ToUpper(MD5(conf.Conf.CasConfig.Passwd)))

	req, err := http.NewRequest("POST", CasLoginUrl, strings.NewReader(body))
	if err != nil {
		return
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.Client.Do(req)
	if err != nil {
		//重试5次
		if count < 5 {
			c.Println("GetCasToken error1,尝试重新登录...count：", count)
			count++
			goto re
		}
		return
	}
	defer resp.Body.Close()
	r, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	var result CasLoginResult
	err = json.Unmarshal(r, &result)
	if err != nil {
		return
	}
	if fmt.Sprint(result.Code) != "200" || result.Content.Token == "" {
		//重试5次
		if count < 5 {
			log.Println("resp.Body:", string(r))
			c.Println("GetCasToken error2,尝试重新登录...count：", count)
			count++
			goto re
		}
		return
	}

	//设置cookie
	cookie := resp.Cookies()
	urlX, _ := url.Parse(CasDomain)
	c.Client.Jar.SetCookies(urlX, cookie)

	tokenStr = result.Content.Token
	c.Println("GetCasToken 登录成功！")
	return
}

type GameInfo struct {
	Code interface{} `json:"code"`
	Data struct {
		PageIndex int `json:"pageIndex"`
		List      []struct {
			AppCode     string `json:"appCode"`
			AppName     string `json:"appName"`
			Category    string `json:"category"`
			CreativeCnt int    `json:"creativeCnt"` //关联广告创意
			Datas       []struct {
				Cnt  int    `json:"cnt"`  //数据
				Name string `json:"name"` //日期
			} `json:"datas"` //30天投放趋势数据
			DatasAvg    float64 `json:"datasAvg"`    //数据平均值
			Developer   string  `json:"developer"`   //开发商
			FindCnt     int     `json:"findCnt"`     //投放天数
			FirstTime   string  `json:"firstTime"`   //开始投放时间
			IconUrl     string  `json:"iconUrl"`     //icon
			Id          string  `json:"id"`          //游戏ID
			LastTime    string  `json:"lastTime"`    //结束投放时间
			MaterialCnt int     `json:"materialCnt"` //投放素材
			MediaList   []struct {
				Cnt  int    `json:"cnt"`  //渠道关联广告创意（渠道广告创意/广告创意=渠道占比）
				Id   int    `json:"id"`   //渠道Id
				Logo string `json:"logo"` //渠道logoUrl
				Name string `json:"name"` //渠道名
			} `json:"mediaList"` //投放渠道列表
			RegionsList []struct {
				Cnt  int    `json:"cnt"`  //地区关联广告创意
				Geo  string `json:"geo"`  //地区缩写
				Logo string `json:"logo"` //地区Logo
				Name string `json:"name"` //地区名
			} `json:"regionsList"` //投放地区列表
		} `json:"list"`
	} `json:"data"`
	Message string `json:"message"`
}
type RequestGameListInfo struct {
	AppRealization []interface{} `json:"appRealization"`
	Category       []interface{} `json:"category"`
	CountryCodes   []interface{} `json:"countryCodes"`
	DataType       int           `json:"dataType"`
	DayMode        string        `json:"dayMode"`
	Devices        []interface{} `json:"devices"`
	Field          string        `json:"field"`
	IsAppointment  []interface{} `json:"isAppointment"`
	IsNew          bool          `json:"isNew"`
	KeyWord        string        `json:"keyWord"`
	MediaIds       []interface{} `json:"mediaIds"`
	Order          string        `json:"order"`
	PageIndex      int           `json:"pageIndex"`
	PageSize       int           `json:"pageSize"`
	WithTrend      bool          `json:"withTrend"`
}

func (c *CasSpider) GetGameInfoList(tokenStr string) (result []GameInfo, err error) {

	var requestBody RequestGameListInfo
	a := []interface{}{}
	requestBody.AppRealization = a
	requestBody.Category = a
	requestBody.CountryCodes = a
	requestBody.Devices = a
	requestBody.IsAppointment = a
	requestBody.MediaIds = a
	requestBody.Field = "2"
	requestBody.DayMode = "D7"
	requestBody.DataType = 1
	requestBody.IsNew = true
	requestBody.Order = "desc"
	requestBody.PageIndex = 1
	requestBody.PageSize = 100
	requestBody.WithTrend = true
	//取最大页数，向上取整
	maxPage := int(math.Ceil(float64(conf.Conf.CasConfig.CollectCount) / float64(requestBody.PageSize)))
	count := 1
re:
	requestBodyStr, err := json.Marshal(requestBody)
	if err != nil {
		return
	}
	req, err := http.NewRequest("POST", CasGameList, strings.NewReader(string(requestBodyStr)))
	if err != nil {
		if count < 5 {
			c.Println("GetGameList error1,尝试重新请求...count：", count)
			count++
			goto re
		}
		return
	}

	req.Header.Set("Authorization", tokenStr)
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50")
	req.Header.Set("Content-Length", strconv.Itoa(strings.Count(string(requestBodyStr), "")))
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
	req.Header.Set("Referer", "http://8.134.139.204/")
	req.Header.Set("Origin", "http://8.134.139.204")
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("language", "cn")

	resp, err := c.Client.Do(req)
	if err != nil {
		if count < 5 {
			c.Println("GetGameList error2,尝试重新请求...count：", count)
			count++
			goto re
		}
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	var tmp GameInfo

	err = json.Unmarshal(body, &tmp)
	if err != nil {
		return
	}
	if fmt.Sprint(tmp.Code) != "200" {
		if count < 5 {
			c.Println("GetGameList error3,尝试重新请求...count：", count)
			count++
			goto re
		}
		return result, errors.New("GetGameList 请求数据失败,code=" + fmt.Sprint(tmp.Code) + ",msg=" + tmp.Message)
	}
	result = append(result, tmp)

	if tmp.Data.PageIndex < maxPage {
		c.Println("GetGameList 获取下一页数据！")
		requestBody.PageIndex++
		goto re
	}

	c.Println("GetGameInfoList 游戏列表采集完成！")
	return
}

type RepuestGameMaterialInfo struct {
	PageIndex   int      `json:"pageIndex"`
	PageSize    int      `json:"pageSize"`
	DataType    string   `json:"dataType"`
	Field       string   `json:"field"`
	Order       string   `json:"order"`
	IsNew       bool     `json:"isNew"`
	KeyWordType string   `json:"keyWordType"`
	KeyWord     string   `json:"keyWord"`
	StartDate   string   `json:"startDate"`
	EndDate     string   `json:"endDate"`
	ProductIds  []string `json:"productIds"`
}
type MaterialInfoResult struct {
	Code interface{} `json:"code"`
	Data struct {
		List []List `json:"list"`
	} `json:"data"`
	Message string `json:"message"`
}
type List struct {
	LastTime    int64   `json:"lastTime"`    //最后投放时间 毫秒时间戳
	Title       string  `json:"title"`       //视频标题
	VideoUrl    string  `json:"videoUrl"`    //视频链接
	FindCnt     int     `json:"findCnt"`     //投放天数
	CreativeCnt int     `json:"creativeCnt"` //关联广告创意
	ConverUrl   string  `json:"converUrl"`   //视频展示图
	FirstTime   int64   `json:"firstTime"`   //开始投放时间 毫秒时间戳
	Describe    string  `json:"describe"`    //视频描述
	HotIndex    float64 `json:"hotIndex"`    //热度
}

func (c *CasSpider) GetGameMaterialInfo(gameId, date, tokenStr string) (result List, err error) {

	a := []string{gameId}
	sDate, err := time.Parse("2006-01-02", date)
	if err != nil {
		return
	}
	r := RepuestGameMaterialInfo{
		1,
		1,
		"1",
		"1", //热度
		"desc",
		true,
		"0,2,3",
		"",
		sDate.AddDate(0, 0, -7).Format("2006-01-02"),
		date,
		a,
	}
	requestBodyStr, err := json.Marshal(r)
	if err != nil {
		return
	}
	count := 0
re:
	req, err := http.NewRequest("POST", CasGameInfo, strings.NewReader(string(requestBodyStr)))
	if err != nil {
		if count < 5 {
			c.Println("GetGameMaterialInfo error1,尝试重新请求...count：", count)
			count++
			goto re
		}
		return
	}
	req.Header.Set("Authorization", tokenStr)
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50")
	req.Header.Set("Content-Length", strconv.Itoa(strings.Count(string(requestBodyStr), "")))
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
	req.Header.Set("Referer", "http://8.134.139.204/")
	req.Header.Set("Origin", "http://8.134.139.204")
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("language", "cn")

	resp, err := c.Client.Do(req)
	if err != nil {
		if count < 5 {
			c.Println("GetGameMaterialInfo error2,尝试重新请求...count：", count)
			count++
			goto re
		}
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	var tmp MaterialInfoResult
	err = json.Unmarshal(body, &tmp)
	if err != nil {
		return
	}
	if fmt.Sprint(tmp.Code) != "200" {
		if count < 5 {
			c.Println("GetGameMaterialInfo error3,尝试重新请求...count：", count)
			count++
			goto re
		}
		return result, errors.New("GetGameMaterialInfo 请求数据失败，code=" + fmt.Sprint(tmp.Code) + ",msg=" + tmp.Message)
	}
	//去除视频和
	if len(tmp.Data.List) > 0 {
		result = tmp.Data.List[0]
		//判断链接中不是视频就设置为空
		if strings.Contains(result.VideoUrl, ".mp4") {
			result.VideoUrl = strings.Split(result.VideoUrl, "?")[0]
		} else {
			result.VideoUrl = ""
		}
		result.ConverUrl = strings.Split(result.ConverUrl, "?")[0]
	}

	return result, nil
}

func (c *CasSpider) collect(date string) (bool, error) {
	tokenStr, err := c.GetCasToken()
	if err != nil || tokenStr == "" {
		c.Println("token获取失败！err = ", err)
		return false, nil
	}

	c.Println("GetGameInfoList 开始采集游戏列表！")
	//获取游戏列表
	gameInfoList, err := c.GetGameInfoList(tokenStr)
	if err != nil {
		//如果是 用户登录限制 的话就报警
		if strings.Contains(err.Error(), "用户登录限制") {
			//如果是 用户登录限制 的话就报警
			err = sendwarn.CasWwarning()
			if err != nil {
				c.Println("collect CasWwarning err:", err)
				return false, err
			}
			//修改报警状态
			CasWarningStatus = true
			return false, err
		}
		return false, err
	}
	var vs []db.CasGameInfo
	c.Println("GetGameInfoList 开始采集游戏相关视频素材信息！")
	for _, v := range gameInfoList {
		for _, v2 := range v.Data.List {
			//获取游戏视频素材信息
			materialInfo, err := c.GetGameMaterialInfo(v2.Id, date, tokenStr)
			if err != nil {
				//如果是 用户登录限制 的话就报警
				if strings.Contains(err.Error(), "用户登录限制") {
					err = sendwarn.CasWwarning()
					if err != nil {
						c.Println("collect CasWwarning err:", err)
						return false, err
					}
					//修改报警状态
					CasWarningStatus = true
					return false, err
				}
				c.Println("GetGameMaterialInfo err=", err)
			}
			datasStr, err := json.Marshal(v2.Datas)
			if err != nil {
				datasStr = []byte("[]")
			}
			mediaListStr, err := json.Marshal(v2.MediaList)
			if err != nil {
				mediaListStr = []byte("[]")
			}
			regionsListStr, err := json.Marshal(v2.RegionsList)
			if err != nil {
				regionsListStr = []byte("[]")
			}
			tmp := db.CasGameInfo{
				v2.AppCode,
				v2.AppName,
				v2.Category,
				v2.CreativeCnt,
				string(datasStr),
				v2.DatasAvg,
				v2.Developer,
				v2.FindCnt,
				v2.FirstTime,
				v2.IconUrl,
				v2.Id,
				v2.LastTime,
				v2.MaterialCnt,
				string(mediaListStr),
				string(regionsListStr),
				materialInfo.LastTime,
				materialInfo.Title,
				materialInfo.VideoUrl,
				materialInfo.FindCnt,
				materialInfo.CreativeCnt,
				materialInfo.ConverUrl,
				materialInfo.FirstTime,
				materialInfo.Describe,
			}
			vs = append(vs, tmp)
			//每次请求间隔3秒，太短会弹验证码
			time.Sleep(time.Duration(conf.Conf.CasConfig.RequestIntervalSecond) * time.Second)
		}
	}
	//存储数据
	err = db.SaveCasData(vs, date)
	if err != nil {
		return false, err
	}
	CasWarningStatus = false
	return true, nil
}

func (this *CasSpider) Run() {
	go func() {
		this.run()
	}()
}

func (c *CasSpider) run() {
	if conf.Conf.Debug.Switch {
		c.Println("CAS 无法采集之前的数据，禁用调试模式！！！")
	} else {
	re:
		h := time.Now().Hour()
		for _, v := range c.ExecutionTime {
			if v == h {
				fDate := time.Now().Format("2006-01-02")
				c.GetDataByDate(fDate)
			}
		}
		time.Sleep(1 * time.Hour)
		goto re
	}
}

func (c *CasSpider) GetDataByDate(date string) {
	c.Println("日期:", date, "开始采集数据...")
	succ, err := c.collect(date)
	if succ {
		c.Println("日期:", date, "采集数据完成 稍后再次采集...")
	} else {
		if err == nil {
			c.Println("日期:", date, "数据未更新 稍后重新采集...")
		} else {
			tool.SendWarn("ASO报警-Cas-overseas爬虫报错", "ASO报警-Cas-overseas爬虫报错,错误信息："+err.Error())
			c.Println("日期:", date, "出现错误err:", err, "稍后重新采集...")
		}
	}
}
